package com.virjar.vscrawler.core.grab.multiaction;

import com.google.common.base.Defaults;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.virjar.vscrawler.core.grab.GrabProcessor;
import com.virjar.vscrawler.core.grab.databind.*;
import com.virjar.vscrawler.core.grab.model.GrabRequest;
import com.virjar.vscrawler.core.grab.model.GrabResult;
import com.virjar.vscrawler.core.net.session.CrawlerSession;
import com.virjar.vscrawler.core.util.CommonUtils;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentMap;

@Slf4j
public class MultiActionProcessor implements GrabProcessor {

    private static final String action = "action";
    private static final String actionList = "_actionList";


    private Map<String, ActionRequestHandlerGenerator> requestHandlerMap = Maps.newHashMap();

    private static final ConcurrentMap<Class, Field[]> fieldCache = Maps.newConcurrentMap();


    @Override
    public GrabResult grab(GrabRequest grabRequest, CrawlerSession crawlerSession) {
        String action = grabRequest.getString(MultiActionProcessor.action);
        if (action == null || action.trim().isEmpty()) {
            action = defaultAction();
        }
        if (action == null || action.trim().isEmpty()) {
            return GrabResult.failed("the param:{" + MultiActionProcessor.action + "} not presented");
        }
        action = actionCaseIgnore() ? action.toLowerCase() : action;
        ActionRequestHandler requestHandler = getActionRequestHandler(action, grabRequest);
        if (requestHandler == null) {
            if (actionList.equalsIgnoreCase(action)) {
                return GrabResult.success(requestHandlerMap.keySet());
            }
            return GrabResult.failed("unknown action: " + action);
        }
        Object handlerResult = requestHandler.handleRequest(grabRequest);
        GrabResult grabResult = tryWrap(handlerResult);
        if (grabResult != null) {
            return grabResult;
        }
        return GrabResult.success(handlerResult);
    }


    /**
     * 存在一个默认的action，为了兼容一些老接口，由于接口刚刚开发的时候，只有一个api。那个时候没有使用多action机制。后来多action机制上线之后，不能影响老的接口
     *
     * @return action名字，默认为Null
     */
    protected String defaultAction() {
        return null;
    }

    /**
     * 在异步场景，等待时间，我们不能无限制的等待异步回调。默认6.5s，为啥6.5。我拍脑门
     *
     * @return 等待时间
     */
    protected long waitTimeout() {
        return 6500L;
    }

    /**
     * action是否忽略大小写，默认忽略
     *
     * @return 是否忽略大小写
     */
    protected boolean actionCaseIgnore() {
        return true;
    }


    private ActionRequestHandler getActionRequestHandler(String action, GrabRequest invokeRequest) {
        ActionRequestHandlerGenerator actionRequestHandlerGenerator = requestHandlerMap.get(action);
        if (actionRequestHandlerGenerator == null) {
            return null;
        }
        return actionRequestHandlerGenerator.gen(invokeRequest);
    }

    private GrabResult tryWrap(Object handlerResult) {
        if (handlerResult == null) {
            return GrabResult.failed(GrabResult.statusReturnNull, "handler return null, wrapper logic error");
        }
        if (handlerResult instanceof GrabResult) {
            return (GrabResult) handlerResult;
        }
        if (handlerResult instanceof Throwable) {
            log.error("handler throw an exception", (Throwable) handlerResult);
            return GrabResult.failed(CommonUtils.translateSimpleExceptionMessage((Throwable) handlerResult));
        }
        return null;
    }


    public void registryHandler(ActionRequestHandler requestHandler) {
        registryHandler(MultiActionWrapperFactory.resolveAction(requestHandler), requestHandler);
    }

    public void registryHandler(String action, ActionRequestHandler requestHandler) {
        Preconditions.checkNotNull(action);
        action = actionCaseIgnore() ? action.toLowerCase() : action;
        if (requestHandlerMap.containsKey(action) && !requestHandlerMap.get(action).equals(requestHandler)) {
            throw new IllegalStateException("the request handler: " + requestHandler + " for action:" + action + "  registered already!!");
        }
        requestHandlerMap.put(action, toGenerator(requestHandler));
    }


    @SuppressWarnings("unchecked")
    private ActionRequestHandlerGenerator toGenerator(ActionRequestHandler actionRequestHandler) {
        Constructor<? extends ActionRequestHandler>[] constructors = (Constructor<? extends ActionRequestHandler>[]) actionRequestHandler.getClass().getDeclaredConstructors();
        boolean canAutoCreateInstance = false;
        ActionRequestHandlerGenerator instanceCreateHelper = null;
        for (Constructor<? extends ActionRequestHandler> constructor : constructors) {
            if (constructor.getParameterTypes().length == 0) {
                canAutoCreateInstance = true;
                instanceCreateHelper = new EmptyARCreateHelper(actionRequestHandler.getClass());
                break;
            }
            if (constructor.getParameterTypes().length == 1 && GrabRequest.class.isAssignableFrom(constructor.getParameterTypes()[0])) {
                canAutoCreateInstance = true;
                instanceCreateHelper = new ICRCreateHelper(constructor);
                break;
            }
        }
        if (!canAutoCreateInstance) {
            return new DirectMapGenerator(actionRequestHandler);
        }

        Field[] fields = classFileds(actionRequestHandler.getClass());
        List<Field> autoBindFields = Lists.newArrayList();
        Map<Field, Object> copyFiledMap = Maps.newHashMap();
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())
                    || field.isSynthetic()) {
                continue;
            }
            try {
                Object o = field.get(actionRequestHandler);
                if (o != null) {
                    if (field.getType().isPrimitive() && Defaults.defaultValue(o.getClass()) == o) {
                        // int a =0;
                        // double =0;
                        continue;
                    }
                    copyFiledMap.put(field, o);
                    //continue;
                }
            } catch (Exception e) {
                //ignore
            }
            autoBindFields.add(field);
        }
        if (autoBindFields.size() == 0) {
            return new DirectMapGenerator(actionRequestHandler);
        }
        return new FieldBindGenerator(autoBindFields, instanceCreateHelper, copyFiledMap);
    }


    private static Field[] classFileds(Class clazz) {
        if (clazz == Object.class) {
            return new Field[0];
        }
        Field[] fields = fieldCache.get(clazz);
        if (fields != null) {
            return fields;
        }
        synchronized (clazz) {
            fields = fieldCache.get(clazz);
            if (fields != null) {
                return fields;
            }
            ArrayList<Field> ret = Lists.newArrayList();
            ret.addAll(Arrays.asList(clazz.getDeclaredFields()));
            ret.addAll(Arrays.asList(classFileds(clazz.getSuperclass())));
            Iterator<Field> iterator = ret.iterator();
            while (iterator.hasNext()) {
                Field next = iterator.next();
                if (Modifier.isStatic(next.getModifiers())) {
                    iterator.remove();
                    continue;
                }
                if (next.isSynthetic()) {
                    iterator.remove();
                    continue;
                }
                if (!next.isAccessible()) {
                    next.setAccessible(true);
                }
            }
            fields = ret.toArray(new Field[0]);

            fieldCache.put(clazz, fields);
        }
        return fields;
    }

}
